#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# -----------------------------------------------------------------------------
# cvisio.py
#
# 

import sys
sys.path.insert(0,"..")

from common.debug import *

from common.cglobal import *
from visioflow import *

STR_YES=u'是'
STR_NO=u'否'

#
# 获取额外的属性，包括未关闭的节点等
#    
def get_extra_node(vexp, extra=0):
    lenv = len(vexp)
    offs = VNODE_TYPE+extra
    # 这是一个君子协定，如果数据不对齐，则退出
    if (lenv > offs):
        return vexp[offs:lenv]
    else:
        return []

#
# 获取表达式的头部
#
def get_exp_head(exp):
    u"""
    @brief 通过递归的方式获取最末端的节点
    """
    node = exp[TK_EXPR]
    while isinstance(node, list):
        node = node[0][TK_EXPR]
    return node

NULL_NODE=-1
BREAK_NODE=-2
CONTINUE_NODE=-3
END_NODE=-10


class cvisio(visiodraw):
    u"""
      visio operator
    """
    # 初始化
    def __init__(self, filename, autoquit=0):
        visiodraw.__init__(self, filename, autoquit)
        self.end_list = []

    def init_end_list(self):
        u"""
        @brief 初始化end_list
        """
        del self.end_list[:]

    def push_end(self, e):
        u"""
        @brief 压入return节点
        @param e return节点
        """
        self.end_list.append(e)

    def conn_jump_node(self, extra, cur, withextra=0):
        u"""
        @brief 根据需要，额外连接CONTINUE等出口
        @param extra
        @param withextra的意思是，需不需要处理continue,break以外的语句
            如果该选项为1，则返回值中将包含continue,break以外的语句
        @return 返回待连接的BREAK语句
        """
        if ENABLE_VISIOD_DEBUG==1: print '====con jump', extra
        rest = []
        for i in range(0,len(extra),2):
            ttype = extra[i]
            tnode = extra[i+1]

            if (ttype == 'RAWBREAK'
                    ):
                # 在循环中，检测到break，将其保存，在出口处使用
                rest.extend(['BREAK', tnode])
            elif (ttype == 'CONTINUE'):
                # 如果在循环中，且检测到CONTINUE，则需要链接到一开始的迭代节点中
                self.conn_node(extra[i+VNODE_PAIR_NODE], cur, CONN_P2N, 'CON')
            else:
                if withextra==1:
                    rest.extend([ttype, tnode])
                pass
        return rest

    def conn_end_node(self, cur):
        u"""
        @brief 根据需要，额外连接RETURN出口
        @param cur  为当前End节点
        """
        for node in self.end_list:
            self.conn_node(node, cur, CONN_P2N)

    def conn_extra_node(self, extra, cur, inloop=0):
        u"""
        @brief 根据需要，连接前一级额外需要连线的节点出口，如else的第二分支等
        @param inloop 表示在循环内部
        """
        if ENABLE_VISIOD_DEBUG==1: print '====con ex', extra
        # 如果extra的长度>=2，则使用前一个节点和当前节点进行连接
        # 判断额外的类型
        rest = []
        for i in range(0,len(extra),2):
            tindex = i+VNODE_PAIR_TYPE
            ttype = extra[tindex]
            tnode = extra[tindex+1]
            #print ttype, 
            if (ttype == 'IF'
                    or ttype == 'WHILE'
                    ):
                self.conn_node(tnode , cur, CONN_D2N, STR_NO)
            elif (ttype == 'LIF'):
                self.conn_node(tnode , cur, CONN_W2W, STR_NO)
            elif (ttype == 'BREAK'
                    ):
                if ENABLE_VISIOD_DEBUG==1: print '====con break', extra
                self.conn_node(tnode , cur, CONN_P2N, "BRK")
            elif (ttype == 'CONTINUE' 
                  #or extra[i+VNODE_PAIR_TYPE] == 'GOTO'
                 ):
                # 如果在循环中，且检测到CONTINUE，则需要链接到一开始的迭代节点中
                #self.conn_node(extra[i+VNODE_PAIR_NODE], cur, CONN_P2N, 'CON')
                rest.extend(extra[tindex:tindex+2])
                pass
            elif (ttype == 'RETURN'):
                # 如果是RETURN，不需要连线
                #self.conn_node(extra[i+VNODE_PAIR_NODE], cur, CONN_P2N)
                pass 
            elif (ttype == 'NODE'):
                self.conn_node(tnode , cur, CONN_P2N)
            else:
                #print "EXTRA LAST", ttype
                rest.extend(extra[tindex:tindex+2])
        #print rest
        return rest

    def draw_alias(self, exp, x, y):
        u"""
        @brief 绘制别名
        @param exp 表达式
        @param x x轴的偏移量
        @param y y轴的偏移量
        """
        count = 0
        ret = []
        cur_node = NULL_NODE
        # 此时直接绘制单节点
        #print "alias", exp[TK_TYPE]
        #if len(exp[TK_EXPR]) > 0:
        if (exp[TK_TYPE][0:4] == 'DONE'):
            #print "alias", exp[TK_TYPE], exp[TK_EXPR]
            try:
                node = exp[TK_EXPR][0]
                node_data = node
                comment = get_exp_head(node_data)
                #print "da", comment

                idx = comment.index('@alias')
                text = comment[idx+7:].rstrip()
                if (text[-1] == '?'):
                    text2 = text[0:-1]+'@'
                cur_node = self.draw_node(NODE_P, x, y+count, text2, node[TK_START])
                #print 'ali', text2
                count += 1
                ret = [0, count, cur_node, cur_node]
                return ret
            except:
                #print exp[TK_EXPR]
                return []
        else:
             return []

    def draw_exp(self, exp, x, y, depth=0, max_depth=DEFAULT_DEPTH):
        u"""
        @brief 绘制表达式，使用递归的方式
        @param exp 表示待绘制的表达式
        @param x x轴的偏移量
        @param y y轴的偏移量
        @param depth 绘制的深度
        @param max_depth 允许的最大绘制深度
        @return 下一次绘制的基础节点[x, y, 入口, 出口]，
            作为下一次的递归的输入
        """
        ttype = exp[TK_TYPE]

        alias = self.draw_alias(exp, x, y)

        if len(alias):
            return alias
        elif depth >= max_depth:
            # 如果层数到达限制，则只绘制简单node，
            # 取第一个节点的注释
            count = 0
            ret = []
            cur_node = NULL_NODE
            if len(exp[TK_EXPR]) > 0:
                node = exp[TK_EXPR][0]
                if (node[TK_TYPE][0:4] != 'DONE'):
                    # 如果不是DONE打头，说明未解释充分，此时直接取第一个元素
                    cur_node = self.draw_node(NODE_P, x, y+count, get_exp_head(node), node[TK_START])
                    count += 1
                    ret = [0, count, cur_node, cur_node]
                else:
                    # 如果以DONE打头，说明已经解释充分，需要遍历相关节点
                    first_node = NULL_NODE
                    last_node = NULL_NODE
                    for node in exp[TK_EXPR]:
                        node_data = node
                        if ENABLE_VISIOD_DEBUG==1: print(node)
                        cur_node = self.draw_node(NODE_P, x, y+count, get_exp_head(node_data), node[TK_START])
                        count += 1
                        if (first_node == NULL_NODE):
                            first_node = cur_node
                        else:
                            # 从第二个节点开始，需要增加连线
                            self.conn_node(last_node, cur_node)
                        last_node = cur_node
                    ret = [0, count, first_node, last_node]
            else:
                ret = [0, 0, NULL_NODE, NULL_NODE]
                #cur_node = self.draw_node(NODE_P, x, y+count, 'to be fill', NULL_NODE)
                #ret = [0, count, cur_node, cur_node]
            # 返回区段的入口、出口
            if ENABLE_VISIOD_DEBUG==1: print('====NODE LEAF', depth, exp[TK_EXPR])
            return ret
        elif (
                ttype == 'DONENODE'
                ):
            count = 0
            ret = []
            first_node = NULL_NODE
            last_node = NULL_NODE
            # 如果是最终节点，直接绘制，需要考虑保存本段的入口和出口
            for node in (exp[TK_EXPR]):
                if ENABLE_DRAW_DEBUG==1: print(node)
                # 在绘制的时候，需要考虑
                cur_node = self.draw_node(NODE_P, x, y+count, node[TK_EXPR], node[TK_START])
                # 如果是第一个节点，需要将其保存
                if count == 0:
                    first_node = cur_node
                    last_node = cur_node
                else:
                    # 其他的节点需要连线
                    self.conn_node(last_node, cur_node)
                last_node = cur_node
                count += 1
            ret = [0, count, first_node, last_node]
            # 返回区段的入口、出口
            return ret
        elif ( ttype == 'DONEBREAK'):
            ret = []
            count = 0
            # 需要判断是否为一个迭代结构的里面
            node = exp[TK_EXPR][0]
            cur_node = self.draw_node(NODE_P, x, y+count, node[TK_EXPR], node[TK_START])
            # 作为break来说，只是将其返回
            #ret = [0, 1, cur_node, cur_node, 'RAWBREAK', cur_node]
            ret = [0, 1, cur_node, BREAK_NODE, 'RAWBREAK', cur_node]
            return ret

        elif ( ttype == 'DONECONTINUE'
                ):
            ret = []
            count = 0
            node = exp[TK_EXPR][0]
            cur_node = self.draw_node(NODE_P, x, y+count, node[TK_EXPR], node[TK_START])
            #ret = [0, 1, cur_node, cur_node]
            ret = [0, 1, cur_node, CONTINUE_NODE, 'CONTINUE', cur_node]
            return ret
        elif (ttype == 'DONEGOTO'):
            ret = []
            count = 0
            node = exp[TK_EXPR][0]
            cur_node = self.draw_node(NODE_P, x, y+count, node[TK_EXPR], node[TK_START])
            ret = [0, 1, cur_node, END_NODE]
            return ret
        elif (ttype == 'DONERETURN'):
            ret = []
            count = 0
            node = exp[TK_EXPR][0]
            cur_node = self.draw_node(NODE_P, x, y+count, node[TK_EXPR], node[TK_START])
            ret = [0, 1, cur_node, END_NODE]
            #ret.extend(['RETURN', cur_node])
            self.push_end(cur_node)
            #cur_node = self.draw_node(NODE_P, x, y+count, node[TK_EXPR], node[TK_START])
            #ret = [0, 1, cur_node, NULL_NODE]
            return ret
        elif (ttype == 'DONEIFELSE'):
            # IF/ELSE结构不需要关注入口，但是需要关注出口
            #    (可能会有需要继承的接口)
            node = exp[TK_EXPR][0]

            if_vnode = self.draw_node(NODE_IF, x, y, node[TK_EXPR], node[TK_START])
            # 获取IF分支的节点
            ret1 = self.draw_exp(exp[TK_EXPR][1], x, y+1, depth+1, max_depth)
            # 获取ELSE函数的节点
            ret2 = self.draw_exp(exp[TK_EXPR][2], x+1+ret1[VNODE_WIDTH], y, depth+1, max_depth)

            self.conn_node(if_vnode, ret1[VNODE_ENTRY], CONN_P2P, STR_YES)
            self.conn_node(if_vnode, ret2[VNODE_ENTRY], CONN_D2P, STR_NO)

            # 在计算else分支的宽度时，需要判断该分支是否空语句，
            #    如果是，取1
            #     如果不是，取0
            if (ret2[VNODE_HEIGHT] != 0):
                width2 = ret2[VNODE_WIDTH] + 1
            else:
                width2 = 0

            # 
            height = max(ret1[VNODE_HEIGHT], ret2[VNODE_HEIGHT]-1)
            width = max(ret1[VNODE_WIDTH], width2)

            # 构建初步的列表
            # 如果else分支为空，则将IF分支作为出口
            if (width2 == 0):
                ret = [width, height+1, if_vnode, ret1[VNODE_EXIT], 'IF', if_vnode]
            else:
                ret = [width, height+1, if_vnode, ret1[VNODE_EXIT], 'NODE', ret2[VNODE_EXIT]]
            # 由于IF/ELSE分支中，IF的出口和ELSE的出口重叠，不需要增加IF的顺序出口
            ret.extend(get_extra_node(ret1))
            #print ret1
            #print exp[TK_EXPR]
            ret.extend(get_extra_node(ret2))
            if ENABLE_VISIOD_DEBUG==1: print 'IFELSE', ret
            return ret

        elif (ttype == 'DONEWHILE'):
            # 表达式
            node = exp[TK_EXPR][0]
            while_vnode = self.draw_node(NODE_IF, x, y, node[TK_EXPR], node[TK_START])
            # 绘制节点
            ret1 = self.draw_exp(exp[TK_EXPR][1], x, y+1, depth+1, max_depth)
            self.conn_node(while_vnode, ret1[VNODE_ENTRY], CONN_P2P, STR_YES)

            # 绘制反馈
            self.conn_node(ret1[VNODE_EXIT], while_vnode, CONN_W2W)
            
            # 构建主要的入口和出口
            ret = [ret1[VNODE_WIDTH], ret1[VNODE_HEIGHT] + 1 , while_vnode, ret1[VNODE_EXIT], ttype[4:len(ttype)], while_vnode]

            extra = get_extra_node(ret1)
            rest = self.conn_jump_node(extra, while_vnode, 1)
            ret.extend(rest)
            return ret

        elif (ttype == 'DONEDEFAULT'):
            node = exp[TK_EXPR][0]
            default_vnode = self.draw_node(NODE_P, x, y, node[TK_EXPR], node[TK_START])
            # 绘制节点
            ret1 = self.draw_exp(exp[TK_EXPR][1], x, y+1, depth+1, max_depth)
            self.conn_node(default_vnode, ret1[VNODE_ENTRY], CONN_P2P)
            
            # 构建主要的入口和出口
            ret = [ret1[VNODE_WIDTH], ret1[VNODE_HEIGHT] + 1 , default_vnode, ret1[VNODE_EXIT], ttype[4:len(ttype)], default_vnode]
            extra = get_extra_node(ret1)
            ret.extend(extra)
            if ENABLE_VISIOD_DEBUG==1: print '====', ttype, extra, ret
            return ret
            
        elif (ttype == 'DONEIF'
                or ttype == 'DONECASE'
                ):
            # 表达式
            node = exp[TK_EXPR][0]
            if_vnode = self.draw_node(NODE_IF, x, y, node[TK_EXPR], node[TK_START])
            # 绘制节点
            ret1 = self.draw_exp(exp[TK_EXPR][1], x, y+1, depth+1, max_depth)
            self.conn_node(if_vnode, ret1[VNODE_ENTRY], CONN_P2P, STR_YES)
            
            # 构建主要的入口和出口
            ret = [ret1[VNODE_WIDTH], ret1[VNODE_HEIGHT] + 1 , if_vnode, ret1[VNODE_EXIT], ttype[4:len(ttype)], if_vnode]
            # 如果分支a的宽度较大，则NO的连线不好连，采用左边连线的方法
            # 此时将extra node的类型设置为LIF，表示左边if
            if (ret1[VNODE_WIDTH] > 1):
                ret[VNODE_TYPE] = 'LIF'

            ret.extend(get_extra_node(ret1))
            if ENABLE_VISIOD_DEBUG==1: print '====', ttype, ret
            return ret

        elif (ttype == 'DONEDO'):
            # 获取if的第一个节点
            node = exp[TK_EXPR][0]
            # 绘制函数体
            ret1 = self.draw_exp(exp[TK_EXPR][1], x, y, depth+1, max_depth)
            # 绘制 while
            if ENABLE_DRAW_DEBUG==1: print('=DO', node, ret1)
            while_vnode = self.draw_node(NODE_IF, x, y+ret1[VNODE_HEIGHT], node[TK_EXPR], node[TK_START])

            self.conn_node(ret1[VNODE_EXIT], while_vnode, CONN_P2P)

            extra = get_extra_node(ret1)
            rest = self.conn_extra_node(extra, while_vnode)
            brk_rest = self.conn_jump_node(rest, while_vnode)

            # while循环需要链接到do
            self.conn_node(while_vnode, ret1[VNODE_ENTRY], CONN_D2P, STR_YES)
            
            ret = [ret1[VNODE_WIDTH], ret1[VNODE_HEIGHT]+1, ret1[VNODE_ENTRY], while_vnode]
            
            ret.extend(brk_rest)
            return ret
        elif (ttype == 'DONEFOR'):
            # 获取 第一个节点
            node = exp[TK_EXPR][0]

            # 绘制初始化函数和判断函数
            vnode0 = self.draw_node(NODE_P, x, y, node[TK_EXPR0], node[TK_START])
            # 判断节点
            for_vnode = self.draw_node(NODE_IF, x, y+1, node[TK_EXPR], node[TK_START])
            self.conn_node(vnode0, for_vnode, CONN_P2P)

            # 绘制函数
            ret1 = self.draw_exp(exp[TK_EXPR][1], x, y+2, depth+1, max_depth)
            self.conn_node(for_vnode, ret1[VNODE_ENTRY], CONN_P2P, STR_YES)

            # 绘制迭代函数
            vnode2 = self.draw_node(NODE_P, x, y+2+ret1[VNODE_HEIGHT], node[TK_EXPR2], node[TK_START])
            self.conn_node(ret1[VNODE_EXIT], vnode2, CONN_P2P)
            extra = get_extra_node(ret1)
            if ENABLE_DRAW_DEBUG==1: print "FOR extra", ret1[VNODE_TYPE:]
            rest = self.conn_extra_node(extra, vnode2)

            brk_rest = self.conn_jump_node(rest, vnode2)

            # 绘制反馈
            self.conn_node(vnode2, for_vnode, CONN_W2W)

            #ret = [ret1[VNODE_WIDTH], ret1[VNODE_HEIGHT]+3, vnode0, vnode2]
            # FOR语句的出口不应该是迭代递增语句, Feb.3 
            ret = [ret1[VNODE_WIDTH], ret1[VNODE_HEIGHT]+3, vnode0, NULL_NODE]
            # 继续扩展
            ret.extend(['IF', for_vnode])

            ret.extend(brk_rest)

            return ret
            
        elif (ttype == 'DONESWITCH'):
            # 将switch接点作为一个初始的工作
            node = exp[TK_EXPR][0]

            # 从通常角度考虑可以理解
            vnode = self.draw_node(NODE_P, x, y, node[TK_EXPR], node[TK_START])

            w = 0
            h = 0
            last_ret=[] # 上一个返回值
            extra=[]    # 
            last_width = 0  # 前一个case语句宽度
            last_height = 0 # 前一个case语句高度
            last_token = 'DONEBREAK'    # 用于判断break连线
            last_branch = 'DONECASE'    # 用于判断break连线
            # 遍历相关节点
            for node2 in (exp[TK_EXPR][1][TK_EXPR]):
                # 遍历switch里面的分支判断
                if (node2[TK_TYPE] == 'DONECASE'
                        or node2[TK_TYPE] == 'DONEDEFAULT'
                        ):
                    # 更新宽度
                    w += last_width
                    last_ret.append(self.draw_exp(node2, x+w, y+1, depth+1, max_depth))
                    if ENABLE_DRAW_DEBUG==1: print 'last', w, last_ret, '\n', node2

                    # 如果不是第一个case，需要和之前的case相连，
                    #   取前case的NO分支，和当前case相连
                    if w!=0:
                        self.conn_node(last_ret[-2][VNODE_ENTRY], last_ret[-1][VNODE_ENTRY], CONN_D2P, STR_NO)
                    last_width = last_ret[-1][VNODE_WIDTH] + 1
                    # 需要更新最高数值
                    last_height = last_ret[-1][VNODE_HEIGHT] + 1
                    if (last_height > h): h = last_height

                    # 如果上一次不是BREAK, 需要将case的最后一个语句连到当前case
                    if (last_token != 'DONEBREAK'):
                        self.conn_node(last_ret[-2][VNODE_EXIT], last_ret[-1][VNODE_ENTRY], CONN_E2P)
                    last_branch = node2
                elif (node2[TK_TYPE] == 'DONEBREAK'):
                    # 除了绘制以外，还需要连接
                    ret2 = self.draw_exp(node2, x+w, y+1 + last_ret[-1][VNODE_HEIGHT], depth+1, max_depth)
                    if ENABLE_DRAW_DEBUG==1: print 'ret2', ret2

                    # 将之前的表达式链接到break上
                    self.conn_node(last_ret[-1][VNODE_EXIT], ret2[VNODE_ENTRY], CONN_P2P)
                    # 将BREAK作为后续的保障
                    extra.extend(['BREAK', ret2[VNODE_ENTRY]])
        
                    # 需要更新最高数值
                    last_height += 1
                    if (last_height > h): h = last_height
                last_token = node2[TK_TYPE]
            # 将switch和第一个case相连
            self.conn_node(vnode, last_ret[0][VNODE_ENTRY], CONN_P2P)

            ret = [w, h, vnode, last_ret[-1][VNODE_EXIT]]

            # 如果之后一个节点不是default，需要额外设置出口
            if (last_branch[TK_TYPE] == "DONECASE"):
                ret.extend(['IF', last_ret[-1][VNODE_ENTRY]])

            # 对于SWITCH而言， 并不存在正规的出口，第一个不包含BREAK语句的CASE就是系统出口
            if (len(extra)>0):
                ret[VNODE_EXIT] = NULL_NODE
            #    ret[VNODE_EXIT] = extra[VNODE_PAIR_NODE]
            ret.extend(extra)
            if ENABLE_DRAW_DEBUG==1: print 'extra h', h, extra
            return ret

        elif (ttype == 'NODE'):
            # 说明需要解析
            count = 0
            width = 0
            ret = []
            extra = []
            rest = []
            first_node = NULL_NODE
            last_node = NULL_NODE
            # 如果是最终节点，直接绘制，需要考虑保存本段的入口和出口
            for node in (exp[TK_EXPR]):
                # 在绘制的时候，需要考虑
                cur_node = self.draw_exp(node, x, y+count, depth+1, max_depth)
                # 如果是第一个节点，需要将其保存
                if count == 0:
                    first_node = cur_node
                    #last_node = cur_node
                else:
                    # 其他的节点需要连线
                    self.conn_node(last_node[VNODE_EXIT], cur_node[VNODE_ENTRY])
                    extra = get_extra_node(last_node)
                    if ENABLE_VISIOD_DEBUG==1: print('====NODE ex', depth, extra)

                    for i in range(0,len(extra),2):
                        if (isinstance(extra[i+VNODE_PAIR_NODE], int)):
                            # 如果连接的时候，某个节点node为整数（-1）
                            # 此时需要将其入口链接到当前接点
                            # 此时认为只有判断表达式才会出现这种问题
                            if ENABLE_VISIOD_DEBUG==1: print('====NODE-1', node, last_node)
                            self.conn_node(last_node[VNODE_ENTRY], cur_node[VNODE_ENTRY], CONN_D2P, STR_NO)

                    rest.extend(self.conn_extra_node(extra, cur_node[VNODE_ENTRY]))

                last_node = cur_node
                count += cur_node[VNODE_HEIGHT]
                width += cur_node[VNODE_WIDTH]
                #print(count, exp[TK_START], cur_node)

            # 返回区段的入口、出口
            ret = [width, count, first_node[VNODE_ENTRY], last_node[VNODE_EXIT]]

            # 将最后一个节点未关闭的导出
            # 此处导致的一个问题是，如果最后一个
            ret.extend(get_extra_node(last_node))

            # 如果有残留的extra node，也需要连接
            if (len(rest)):
                ret.extend(rest)

            #if ENABLE_VISIOD_DEBUG==1: print('====NODE ret', ret)
            if ENABLE_VISIOD_DEBUG==1: print('====NODE ret', ret)
            return ret

    def draw_fun(self, name, exp, x, y, max_depth=100):
        u"""
        @brief 绘制函数
        @param name 函数名称
        @param exp 对应表达式
        @param x
        @param y
        """
        # 新建页面
        self.new_page(name)
        self.init_end_list()
        # 绘制起始节点
        start = self.draw_node(NODE_END, x, y, 'Start', NULL_NODE)
        y += 1
        # 绘制表达式
        #print 'final', exp
        ret = self.draw_exp(exp, x, y, 0, max_depth)
        self.conn_node(start, ret[VNODE_ENTRY])

        rnode = self.draw_node(NODE_END, x, y+ret[VNODE_HEIGHT], 'End', NULL_NODE)
        self.conn_node(ret[VNODE_EXIT], rnode)
        # 需要绘制之前分支未结束的任务
        extra = get_extra_node(ret)
        self.conn_extra_node(extra, rnode)
        # 需要连接return的节点
        self.conn_end_node(rnode)

vdraw=cvisio(get_full_name('_def.vsd'))

if __name__ == "__main__":
    print "gogogo"
